﻿Imports System.ServiceModel.Configuration
Imports System.Configuration
Imports System.ServiceModel
Imports System.Security.Cryptography

<ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single, ConcurrencyMode:=ConcurrencyMode.Single, IncludeExceptionDetailInFaults:=True)>
Public MustInherit Class BaseEncryptedService
    Implements IMysteryEncryptedService

    Private _name As String


    Private messageHandlers As New Dictionary(Of Type, Func(Of User, Object, Object))

    ''' <summary>
    ''' instance
    ''' </summary>
    ''' <param name="name">name to be found in the app.config</param>
    ''' <remarks></remarks>
    Public Sub New(name As String)
        _name = name
    End Sub

    Public Sub registerHandler(Of InputType, ResultType)(handler As Func(Of User, InputType, ResultType))
        Dim input_type As Type = GetType(InputType)
        Dim f As Func(Of User, Object, Object) = Function(user As User, x As Object)
                                                     Return handler(user, x)
                                                 End Function
        messageHandlers(input_type) = f
    End Sub

    Public Sub registerHandler(Of InputType, ResultType)(handler As Func(Of User, ResultType))
        Dim input_type As Type = GetType(InputType)
        Dim f As Func(Of User, Object, Object) = Function(user As User, x As Object)
                                                     Return handler(user)
                                                 End Function
        messageHandlers(input_type) = f
    End Sub

    Public Sub registerHandler(Of InputType, ResultType)(handler As Func(Of InputType, ResultType))
        Dim input_type As Type = GetType(InputType)
        Dim f As Func(Of User, Object, Object) = Function(user As User, x As Object)
                                                     Return handler(x)
                                                 End Function
        messageHandlers(input_type) = f
    End Sub

    Public Sub registerHandler(Of InputType)(handler As Action(Of InputType))
        Dim input_type As Type = GetType(InputType)
        Dim f As Func(Of User, Object, Object) = Function(user As User, x As Object)
                                                     handler(x)
                                                     Return Nothing
                                                 End Function
        messageHandlers(input_type) = f
    End Sub

    Public Sub registerHandler(Of InputType)(handler As Action(Of User, InputType))
        Dim input_type As Type = GetType(InputType)
        Dim f As Func(Of User, Object, Object) = Function(user As User, x As Object)
                                                     handler(user, x)
                                                     Return "done"
                                                 End Function
        messageHandlers(input_type) = f
    End Sub

    

    Public Sub start()
        Console.WriteLine(Me.GetType().FullName & " starting")
        Dim group As ServiceModelSectionGroup = ServiceModelSectionGroup.GetSectionGroup(ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None))
        If group IsNot Nothing Then
            Dim service As ServiceElement = (From x As ServiceElement In group.Services.Services Select x Where x.Name = _name).FirstOrDefault
            Dim endpoit As ServiceEndpointElement = service.Endpoints(0)
            Dim baseAddress As String = endpoit.Address.AbsoluteUri.Replace(endpoit.Address.AbsolutePath, String.Empty)
            Dim host As New ServiceHost(Me, {New Uri(baseAddress)})
            host.AddServiceEndpoint(GetType(IMysteryEncryptedService), New NetTcpBinding("tcp"), endpoit.Address.AbsolutePath)
            host.Open()
        End If
    End Sub

    Public Function message(input As WebMessage) As WebMessage Implements IMysteryEncryptedService.message
        Try
            If input Is Nothing Then Return Nothing
            Dim sender As String = input.Sender
            Dim cd As IContentDispatcher = getGlobalObject(Of IContentDispatcher)()
            Dim client As Client = cd.getAllByFilter(Of Client)(New Client With {.name = New ContentPropertyFilter(Of String)(sender),
                                                                                 .Token = New ContentPropertyFilter(Of String)(input.Token.readAEncryptedString)}).FirstOrDefault
            If client Is Nothing Then Return Nothing
            Dim client_rsa As RSACryptoServiceProvider = client.getRsa
            If client_rsa Is Nothing Then Return Nothing


            Dim message_object As Object = WebMessage.readMessage(input, client_rsa)
            Dim input_type As Type = message_object.GetType
            If Not messageHandlers.ContainsKey(input_type) Then Return Nothing

            Dim user As User = cd.getAllByFilter(Of User)(New User With {.username = New ContentPropertyFilter(Of String)(sender)}).FirstOrDefault

            Dim result As Object = messageHandlers(input_type)(user, message_object)

            Dim answer As WebMessage = WebMessage.GenerateMessage("server", result, client_rsa, "server")

            Return answer
        Catch ex As Exception
            Return Nothing
        End Try


    End Function


    Public Function getPublicKey() As getPublicKeyOutput Implements IMysteryEncryptedService.getPublicKey
        Return New getPublicKeyOutput With {.XmlString = getGlobalObject(Of IMyRsaProvider)().getMySra.ToXmlString(False)}
    End Function

    Public Function Authenticate(input As AuthenticateInput) As AuthenticateOutput Implements IMysteryEncryptedService.Authenticate
        If input Is Nothing Then Return Nothing

        Dim sender As String = input.username
        Dim result As New AuthenticateOutput
        Dim client_rsa As New RSACryptoServiceProvider
        client_rsa.PersistKeyInCsp = False
        Dim token As String = Guid.NewGuid.ToString

        Try
            client_rsa.FromXmlString(input.PublicKeyXml)
            result.token = client_rsa.Encrypt(token.getBytes, False)
        Catch ex As Exception
            Return New AuthenticateOutput With {.success = False, .message = "the provided public key cannot be used"}
        End Try

        'for the moment the users have to be mono client

        Dim cd As IContentDispatcher = getGlobalObject(Of IContentDispatcher)()
        Dim client As Client = cd.getAllByFilter(Of Client)(New Client With {.name = New ContentPropertyFilter(Of String)(sender)}).FirstOrDefault
        If client Is Nothing Then
            Return New AuthenticateOutput With {.success = False, .message = "user not authorized to log in from client"}
        End If

        Dim au As IAuthetication = getGlobalObject(Of IAuthetication)()
        Dim user As User = au.autheticate(input.username, input.password.readAEncryptedString())
        If user Is Nothing Then
            Return New AuthenticateOutput With {.success = False, .message = "username or password invalid"}
        End If

        'last step, does this specific service allow this user, client and public key?
        If Not Me.Authorize(user, client, input.PublicKeyXml, result.message) Then
            Return New AuthenticateOutput With {.success = False, .message = result.message}
        End If

        client.XmlString = input.PublicKeyXml
        client.Token = token
        cd.Add(client)
        result.success = True
        Return result
    End Function

    ''' <summary>
    ''' override for final verification
    ''' </summary>
    ''' <param name="user"></param>
    ''' <param name="client"></param>
    ''' <param name="publicKey"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public MustOverride Function Authorize(user As User, client As Client, publicKey As String, Optional ByRef message As String = "") As Boolean

    Public Function HelloWorld() As String Implements IMysteryEncryptedService.HelloWorld
        Return "hello!"
    End Function
End Class
